Micron Document
Gopher Proxy


gopherpedia.com:70 gopherpedia.com:70/0//Type punning
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

======================================================================
= Type punning =
======================================================================

Introduction
======================================================================
In computer science, type punning is any programming technique that
subverts or circumvents the type system of a programming language in
order to achieve an effect that would be difficult or impossible to
achieve within the bounds of the formal language.

In C and C++, constructs such as pointer type conversion and union --
C++ adds reference type conversion and reinterpret_cast to this list
-- are provided in order to permit many kinds of type punning,
although some kinds are not actually supported by the standard
language.

In the Pascal programming language, the use of records with variants
may be used to treat a particular data type in more than one manner,
or in a manner not normally permitted.


Sockets example
======================================================================
One classic example of type punning is found in the Berkeley sockets
interface. The function to bind an opened but uninitialized socket to
an IP address is declared as follows:


int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);


The bind function is usually called as follows:


#include

struct sockaddr_in sa = {
..sin_family = AF_INET,
..sin_port = htons(port)
};

int sockfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
bind(sockfd, (struct sockaddr*)&sa, sizeof sa);


The Berkeley sockets library fundamentally relies on the fact that in
C, a pointer to struct sockaddr_in is freely convertible to a pointer
to struct sockaddr; and, in addition, that the two structure types
share the same memory layout. Therefore, a reference to the structure
field my_addr->sin_family (where my_addr is of type struct
sockaddr*) will actually refer to the field sa.sin_family (where sa is
of type struct sockaddr_in). In other words, the sockets library uses
type punning to implement a rudimentary form of polymorphism or
inheritance.

Often seen in the programming world is the use of "padded" data
structures to allow for the storage of different kinds of values in
what is effectively the same storage space. This is often seen when
two structures are used in mutual exclusivity for optimization.


Floating-point example
======================================================================
Not all examples of type punning involve structures, as the previous
example did. Suppose we want to determine whether a floating-point
number is negative. We could write:


bool is_negative(float x) {
return x < 0.0f;
}


However, supposing that floating-point comparisons are expensive, and
also supposing that float is represented according to the IEEE
floating-point standard, and integers are 32 bits wide, we could
engage in type punning to extract the sign bit of the floating-point
number using only integer operations:


bool is_negative(float x) {
int* i = (int*)&x;
return *i < 0;
}


Note that the behaviour will not be exactly the same: in the special
case of x being negative zero, the first implementation yields false
while the second yields true. Also, the first implementation will
return false for any NaN value, but the latter might return true for
NaN values with the sign bit set. Lastly we have the problem wherein
the storage of the floating point data may be in big endian or little
endian memory order and thus the sign bit could be in the least
significant byte or the most significant byte. Therefore the use of
type punning with floating point data is a questionable method with
unpredictable results.

This kind of type punning is more dangerous than most. Whereas the
sockets example relied only on guarantees made by the C programming
language about structure layout and pointer convertibility, the float
example relies on assumptions about a particular system's hardware.
The C99 Language Specification ( ISO9899:1999 ) has the following
warning in section 6.3.2.3 Pointers : "A pointer to an object or
incomplete type may be converted to a pointer to a different object or
incomplete type. If the resulting pointer is not correctly aligned for
the pointed-to type, the behavior is undefined." Therefore one should
be very careful with the use of type punning.

Some situations, such as time-critical code that the compiler
otherwise fails to optimize, may require dangerous code. In these
cases, documenting all such assumptions in comments, and introducing
static assertions to verify portability expectations, helps to keep
the code maintainable.

Practical examples of floating-point punning include fast inverse
square root popularized by Quake III, fast FP comparison as integers,
and finding neighboring values by incrementing as an integer
(implementing ).


C and C++
===========
In addition to the assumption about bit-representation of
floating-point numbers, the above floating-point type-punning example
also violates the C language's constraints on how objects are
accessed: the declared type of x is float but it is read through an
expression of type unsigned int. On many common platforms, this use
of pointer punning can create problems if different pointers are
aligned in machine-specific ways. Furthermore, pointers of different
sizes can alias accesses to the same memory, causing problems that are
unchecked by the compiler. Even when data size and pointer
representation match, however, compilers can rely on the non-aliasing
constraints to perform optimizations that would be unsafe in the
presence of disallowed aliasing.


Use of pointers
=================
A naive attempt at type-punning can be achieved by using pointers:
(The following running example assumes IEEE-754 bit-representation for
type float.)


// in C
bool is_negative(float x) {
int32_t i = *(int32_t*)&x;
return i < 0;
}

// in C++
bool is_negative(float x) {
int32_t i = *reinterpret_cast(&x);
return i < 0;
}


The C standard's aliasing rules state that an object shall have its
stored value accessed only by an lvalue expression of a compatible
type. The types float and int32_t are not compatible, therefore this
code's behavior is undefined. Although on GCC and LLVM this particular
program compiles and runs as expected, more complicated examples may
interact with assumptions made by strict aliasing and lead to unwanted
behavior. The option -fno-strict-aliasing will ensure correct behavior
of code using this form of type-punning, although using other forms of
type punning is recommended.


Use of <code>union</code>
===========================
In C, but not in C++, it is sometimes possible to perform type punning
via a union.


bool is_negative(float x) {
union {
int i;
float f;
} my_union;
my_union.f = x;
return my_union.i &lt; 0;
}


Accessing my_union.i after most recently writing to the other member,
my_union.f, is an allowed form of type-punning in C, provided that the
member read is not larger than the one whose value was set (otherwise
the read has unspecified behavior
). The same is syntactically valid but has undefined behavior in C++,
where only the last-written member of a union is considered to have
any value at all.

For another example of type punning, see Stride of an array.


Use of <code>memcpy</code>
============================
memcpy is a safe and portable method of type punning, blessed in the
C++ standard. Clang and GCC include specific optimizations for memcpy
calls of this sort.


#include

bool is_negative(float x) {
int i;
memcpy(&amp;i, &amp;x, sizeof(int)); // or std::memcpy in C++
return i &lt; 0;
}


Use of <code>bit_cast</code>
==============================
In C++20, the std::bit_cast function allows type punning with no
undefined behavior. It also allows the function be labeled constexpr.
The reference implementation is a wrapper around std::memcpy.


import std;

using std::numeric_limits;

constexpr bool is_negative(float x) noexcept {
static_assert(numeric_limits::is_iec559); // (enable only on IEEE
754)
int32_t i = std::bit_cast(x);
return i &lt; 0 ;
}


Another type punning mechanism included to C++ in C++23 with and .


Pascal
========
A variant record permits treating a data type as multiple kinds of
data depending on which variant is being referenced. In the following
example, 'integer' is presumed to be 16 bit, while 'longint' and
'real' are presumed to be 32, while character is presumed to be 8 bit:

type
VariantRecord = record
case RecType : LongInt of
1: (I : array[1..2] of Integer); (* not show here: there
can be several variables in a variant record's case statement *)
2: (L : LongInt );
3: (R : Real );
4: (C : array[1..4] of Char );
end;

var
V : VariantRecord;
K : Integer;
LA : LongInt;
RA : Real;
Ch : Character;

V.I[1] := 1;
Ch := V.C[1]; (* this would extract the first byte of V.I *)
V.R := 8.3;
LA := V.L; (* this would store a Real into an Integer *)

In Pascal, copying a real to an integer converts it to the truncated
value. This method would translate the binary value of the
floating-point number into whatever it is as a long integer (32 bit),
which will not be the same and may be incompatible with the long
integer value on some systems.

These examples could be used to create strange conversions, although,
in some cases, there may be legitimate uses for these types of
constructs, such as for determining locations of particular pieces of
data. In the following example a pointer and a longint are both
presumed to be 32 bit:

type
PA = ^Arec;

Arec = record
case RT : LongInt of
1: (P : PA );
2: (L : LongInt);
end;

var
PP : PA;
K : LongInt;

New(PP);
PP^.P := PP;
WriteLn('Variable PP is located at address ', Hex(PP^.L));

Where "new" is the standard routine in Pascal for allocating memory
for a pointer, and "hex" is presumably a routine to print the
hexadecimal string describing the value of an integer. This would
allow the display of the address of a pointer, something which is not
normally permitted. (Pointers cannot be read or written, only
assigned.) Assigning a value to an integer variant of a pointer would
allow examining or writing to any location in system memory:

PP^.L := 0;
PP := PP^.P; (* PP now points to address 0 *)
K := PP^.L; (* K contains the value of word 0 *)
WriteLn('Word 0 of this machine contains ', K);

This construct may cause a program check or protection violation if
address 0 is protected against reading on the machine the program is
running upon or the operating system it is running under.

The reinterpret cast technique from C/C++ also works in Pascal. This
can be useful, when eg. reading dwords from a byte stream, and we want
to treat them as float. Here is a working example, where we
reinterpret-cast a dword to a float:

type
pReal = ^Real;

var
DW : DWord;
F : Real;

F := pReal(@DW)^;


C#
====
In C# (and other .NET languages), type punning is a little harder to
achieve because of the type system, but can be done nonetheless, using
pointers or struct unions.


Pointers
==========
C# only allows pointers to so-called native types, i.e. any primitive
type (except string), enum, array or struct that is composed only of
other native types. Note that pointers are only allowed in code blocks
marked 'unsafe'.


unsafe
{
float pi = 3.14159;
uint piAsRawData = *(uint*)π
}


Struct unions
===============
Struct unions are allowed without any notion of 'unsafe' code, but
they do require the definition of a new type.


[StructLayout(LayoutKind.Explicit)]
struct FloatAndUIntUnion
{
[FieldOffset(0)]
public float DataAsFloat;

[FieldOffset(0)]
public uint DataAsUInt;
}

// ...

FloatAndUIntUnion union;
union.DataAsFloat = 3.14159;
uint piAsRawData = union.DataAsUInt;


Raw CIL code
==============
Raw CIL can be used instead of C#, because it doesn't have most of the
type limitations. This allows one to, for example, combine two enum
values of a generic type:


TEnum a = ...;
TEnum b = ...;
TEnum combined = a | b; // illegal


This can be circumvented by the following CIL code:


..method public static hidebysig
!!TEnum CombineEnums(
!!TEnum a,
!!TEnum b
) cil managed
{
..maxstack 2

ldarg.0
ldarg.1
or // this will not cause an overflow, because a and b have the
same type, and therefore the same size.
ret
}


The cpblk CIL opcode allows for some other tricks, such as converting
a struct to a byte array:


..method public static hidebysig
uint8[] ToByteArray(
!!T&amp; v // 'ref T' in C#
) cil managed
{
..locals init (
[0] uint8[]
)

..maxstack 3

// create a new byte array with length sizeof(T) and store it in
local 0
sizeof !!T
newarr uint8
dup // keep a copy on the stack for later (1)
stloc.0

ldc.i4.0
ldelema uint8

// memcpy(local 0, &amp;v, sizeof(T));
//
ldarg.0 // this is the *address* of 'v', because its type is
'!!T&amp;'
sizeof !!T
cpblk

ldloc.0
ret
}


Java
======
Unlike C/C++ which offer memory access and pointer arithmetic, Java
does not (officially) support these. However, type punning can be
similarly simulated.


Using <code>java.nio.ByteBuffer</code>
========================================
import java.nio.ByteBuffer;

void main(String[] args) {
int value = 42;

ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES);
buffer.putInt(value);

// "Pun" this data into a float (just as an example)
buffer.flip();
float punResult = buffer.getFloat();
}


Using <code>sun.misc.Unsafe</code>
====================================
Using Security of the Java software platform#The sun.misc.Unsafe
class, type punning can be done directly, using direct memory access.


import java.lang.reflect.Field;
import sun.misc.Unsafe;

void main(String[] args) throws NoSuchFieldException,
IllegalAccessException {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);

long address = unsafe.allocateMemory(4);
unsafe.putInt(address, 42);

// Interpret the memory location as a float
float result = unsafe.getFloat(address);

unsafe.freeMemory(address);
}


External links
======================================================================
*[https://gcc.gnu.org/onlinedocs/gcc-8.2.0/gcc/Optimize-Options.html#index-fstrict-aliasing
Section] of the GCC manual on -fstrict-aliasing, which defeats some
type punning
*[http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_257.htm Defect
Report 257] to the C99 standard, incidentally defining "type punning"
in terms of union, and discussing the issues surrounding the
implementation-defined behavior of the last example above
*[http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_283.htm Defect
Report 283] on the use of unions for type punning


License
=========
All content on Gopherpedia comes from Wikipedia, and is licensed under CC-BY-SA
License URL: http://creativecommons.org/licenses/by-sa/3.0/
Original Article: http://en.wikipedia.org/wiki/Type_punning


.

you're on gopherpedia.com^70/0//Type punning
back link is gopherpedia.com:70/1/